/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h"
#include "plugins/ecmascript/runtime/global_env_constants-inl.h"
#include "plugins/ecmascript/runtime/ic/properties_cache-inl.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/interpreter/interpreter-inl.h"
#include "runtime/include/panda_vm.h"
#include "runtime/include/stack_walker-inl.h"

namespace ark::ecmascript {
// static
JSThread *JSThread::Create(Runtime *runtime, PandaVM *vm)
{
    auto jsThread = new JSThread(runtime, vm);
    jsThread->InitBuffers();
    JSThread::SetCurrent(jsThread);
    jsThread->NativeCodeBegin();
    jsThread->InitForStackOverflowCheck(STACK_OVERFLOW_RESERVED_SIZE, STACK_OVERFLOW_PROTECTED_SIZE);
    return jsThread;
}

JSThread::JSThread(Runtime *runtime, PandaVM *vm)
    : ManagedThread(GetCurrentThreadId(), runtime->GetInternalAllocator(), vm, Thread::ThreadType::THREAD_TYPE_MANAGED,
                    panda_file::SourceLang::ECMASCRIPT)
{
    SetLanguageContext(runtime->GetLanguageContext(panda_file::SourceLang::ECMASCRIPT));
    globalStorage_ = runtime->GetInternalAllocator()->New<EcmaGlobalStorage>();
    internalCallParams_ = new InternalCallParams();
    propertiesCache_ = new PropertiesCache();
}

JSThread::~JSThread()
{
    for (auto n : handleStorageNodes_) {
        delete n;
    }
    handleStorageNodes_.clear();
    currentHandleStorageIndex_ = -1;
    handleScopeCount_ = 0;
    handleScopeStorageNext_ = handleScopeStorageEnd_ = nullptr;
    Runtime::GetCurrent()->GetInternalAllocator()->Delete(globalStorage_);

    if (internalCallParams_ != nullptr) {
        delete internalCallParams_;
        internalCallParams_ = nullptr;
    }
    if (propertiesCache_ != nullptr) {
        delete propertiesCache_;
        propertiesCache_ = nullptr;
    }
}

EcmaVM *JSThread::GetEcmaVM() const
{
    return EcmaVM::Cast(GetVM());
}

void JSThread::SetException(JSTaggedValue exception)
{
    ManagedThread::SetException(exception.GetHeapObject());
}

void JSThread::ClearException()
{
    ManagedThread::ClearException();
}

void JSThread::Iterate(const RootVisitor &v0, const RootRangeVisitor &v1)
{
    auto *exc = ManagedThread::GetException();
    if (!JSTaggedValue(exc).IsHole()) {
        v0(Root::ROOT_VM, ObjectSlot(ToUintPtr(exc)));
        v0(Root::ROOT_VM,
           ObjectSlot(ToUintPtr(static_cast<ManagedThread *>(this)) + ManagedThread::GetExceptionOffset()));
    }

    for (auto stack = StackWalker::Create(this); stack.HasFrame(); stack.NextFrame()) {
        stack.IterateObjects([&v0](auto &vreg) {
            v0(Root::ROOT_FRAME, ObjectSlot(ToUintPtr(vreg.GetReference())));
            return true;
        });
    }
    IterateEcmascriptEnvironment(v0, v1);
    v0(Root::ROOT_VM, ObjectSlot(ToUintPtr(&invocationLexicalEnv_)));

    // visit internal call params；
    internalCallParams_->Iterate(v1);
    // visit tagged handle storage roots
    if (currentHandleStorageIndex_ != -1) {
        int32_t nid = currentHandleStorageIndex_;
        for (int32_t i = 0; i <= nid; ++i) {
            auto node = handleStorageNodes_.at(i);
            auto start = node->data();
            auto end = (i != nid) ? &(node->data()[NODE_BLOCK_SIZE]) : handleScopeStorageNext_;
            v1(ecmascript::Root::ROOT_HANDLE, ObjectSlot(ToUintPtr(start)), ObjectSlot(ToUintPtr(end)));
        }
    }
    globalStorage_->IterateUsageGlobal([v0](EcmaGlobalStorage::Node *node) {
        JSTaggedValue value(node->GetObject());
        if (value.IsHeapObject()) {
            v0(ecmascript::Root::ROOT_HANDLE, ecmascript::ObjectSlot(node->GetObjectAddress()));
        }
    });

    JSSpanHandle::IterateChain(this, v1);
    ScopedCallInfo::IterateChain(this, v1);
}

void JSThread::IterateEcmascriptEnvironment(const RootVisitor &v0, const RootRangeVisitor &v1)
{
    auto adapter = [&v1]([[maybe_unused]] ObjectHeader *obj, ObjectSlot start, ObjectSlot end) {
        v1(Root::ROOT_VM, start, end);
    };
    auto visitEnv = [&v0, &adapter](EcmascriptEnvironment *env) {
        v0(Root::ROOT_VM, ObjectSlot(ToUintPtr(env) + EcmascriptEnvironment::GetConstantPoolOffset()));
        v0(Root::ROOT_VM, ObjectSlot(ToUintPtr(env) + EcmascriptEnvironment::GetLexicalEnvOffset()));
        v0(Root::ROOT_VM, ObjectSlot(ToUintPtr(env) + EcmascriptEnvironment::GetThisFuncOffset()));

        JSTaggedValue lexEnv(env->GetLexicalEnv());
        while (!lexEnv.IsJSGlobalEnv()) {
            LexicalEnv::Cast(lexEnv.GetHeapObject())->VisitRangeSlot(adapter);
            lexEnv = LexicalEnv::Cast(lexEnv.GetHeapObject())->GetParentEnv();
        }
    };

    for (auto stack = StackWalker::Create(this); stack.HasFrame(); stack.NextFrame()) {
        if (!stack.IsCFrame()) {
            auto iframe = stack.GetIFrame();
            if (iframe->GetExt() != reinterpret_cast<void *>(iframe)) {
                visitEnv(JSFrame::GetJSEnv(iframe));
            }
        }
    }

    if (propertiesCache_ != nullptr) {
        propertiesCache_->Clear();
    }

    // visit global Constant
    globalConst_.VisitRangeSlot(v1);
}

uintptr_t *JSThread::ExpandHandleStorage()
{
    uintptr_t *result = nullptr;
    int32_t lastIndex = handleStorageNodes_.size() - 1;
    if (currentHandleStorageIndex_ == lastIndex) {
        auto n = new std::array<JSTaggedType, NODE_BLOCK_SIZE>();
        handleStorageNodes_.push_back(n);
        currentHandleStorageIndex_++;
        result = reinterpret_cast<uintptr_t *>(&n->data()[0]);
        handleScopeStorageEnd_ = &n->data()[NODE_BLOCK_SIZE];
    } else {
        currentHandleStorageIndex_++;
        auto lastNode = handleStorageNodes_[currentHandleStorageIndex_];
        result = reinterpret_cast<uintptr_t *>(&lastNode->data()[0]);
        handleScopeStorageEnd_ = &lastNode->data()[NODE_BLOCK_SIZE];
    }

    return result;
}

void JSThread::ShrinkHandleStorage(int prevIndex)
{
    currentHandleStorageIndex_ = prevIndex;
    int32_t lastIndex = handleStorageNodes_.size() - 1;
#if ECMASCRIPT_ENABLE_ZAP_MEM
    uintptr_t size = ToUintPtr(handle_scope_storage_end_) - ToUintPtr(handle_scope_storage_next_);
    memset_s(handle_scope_storage_next_, size, 0, size);
    for (int32_t i = current_handle_storage_index_ + 1; i < last_index; i++) {
        memset_s(handle_storage_nodes_[i], NODE_BLOCK_SIZE * sizeof(JSTaggedType), 0,
                 NODE_BLOCK_SIZE * sizeof(JSTaggedType));
    }
#endif

    if (lastIndex > MIN_HANDLE_STORAGE_SIZE && currentHandleStorageIndex_ < MIN_HANDLE_STORAGE_SIZE) {
        for (int i = MIN_HANDLE_STORAGE_SIZE; i < lastIndex; i++) {
            auto node = handleStorageNodes_.back();
            delete node;
            handleStorageNodes_.pop_back();
        }
    }
}

void JSThread::NotifyStableArrayElementsGuardians(JSHandle<JSObject> receiver)
{
    if (!receiver->GetJSHClass()->IsPrototype()) {
        return;
    }
    if (!stableArrayElementsGuardians_) {
        return;
    }
    auto env = GetEcmaVM()->GetGlobalEnv();
    if (receiver.GetTaggedValue() == env->GetObjectFunctionPrototype().GetTaggedValue() ||
        receiver.GetTaggedValue() == env->GetArrayPrototype().GetTaggedValue()) {
        stableArrayElementsGuardians_ = false;
    }
}

void JSThread::ResetGuardians()
{
    stableArrayElementsGuardians_ = true;
}

void JSThread::DisableStackOverflowCheck()
{
    GetStackFrameAllocator()->UseWholeMemory();
    ManagedThread::DisableStackOverflowCheck();
}

void JSThread::EnableStackOverflowCheck()
{
    GetStackFrameAllocator()->ReserveMemory();
    ManagedThread::EnableStackOverflowCheck();
}

}  // namespace ark::ecmascript
